package cn.lxh.system.service.impl;


import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.lxh.exception.BusinessException;
import cn.lxh.system.domain.Logininfo;
import cn.lxh.system.domain.Menu;
import cn.lxh.system.dto.LoginDto;
import cn.lxh.system.jwt.JwtUtil;
import cn.lxh.system.jwt.Payload;
import cn.lxh.system.mapper.LogininfoMapper;
import cn.lxh.system.query.LogininfoQuery;
import cn.lxh.system.service.ILogininfoService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * LogininfoServiceImpl业务层类
 */
@Service // 该类交给spring管理
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
// 将数据库设置成只读数据库, 此时若要进行写的操作, 会出现错误, 有事务就以事务方式执行, 没有事务就以非事务方式执行
public class LogininfoServiceImpl implements ILogininfoService {

    @Autowired
    private LogininfoMapper logininfoMapper;

    /**
     * 修改登录信息
     *
     * @param logininfo
     */
    @Transactional // 开始事务
    @Override
    public void add(Logininfo logininfo) {
        logininfoMapper.add(logininfo);
    }

    /**
     * 根据id删除登录信息
     *
     * @param id
     */
    @Transactional // 开始事务
    @Override
    public void delete(Long id) {
        logininfoMapper.delete(id);
    }

    /**
     * 修改登录信息
     *
     * @param logininfo
     */
    @Transactional // 开始事务
    @Override
    public void update(Logininfo logininfo) {
        logininfoMapper.update(logininfo);
    }

    /**
     * 根据id查询单个登录信息
     *
     * @param id
     * @return
     */
    @Override
    public Logininfo findOne(Long id) {
        return logininfoMapper.findOne(id);
    }

    /**
     * 查询所有登录信息
     *
     * @return
     */
    @Override
    public List<Logininfo> findAll() {
        return logininfoMapper.findAll();
    }

    /**
     * 根据id批量删除登录信息
     *
     * @param ids
     */
    @Transactional // 开始事务
    @Override
    public void patchDelete(Long[] ids) {
        logininfoMapper.patchDelete(ids);
    }

    /**
     * 分页查询
     *
     * @param logininfoQuery
     * @return
     */
    @Override
    public PageInfo<Logininfo> queryPage(LogininfoQuery logininfoQuery) {
        // 开启分页, 它只对紧挨着下面的第一个查询所有的sql语句生效(1. 先查询总数量total 2. 根据application.yml中驱动名自动实现分页,如果是mysql就用limit
        // 3. 自动计算limit的第一个值: limit a, b
        PageHelper.startPage(logininfoQuery.getCurrentPage(), logininfoQuery.getPageSize());
        // 查询所有
        List<Logininfo> list = logininfoMapper.queryPage(logininfoQuery);
        // 返回对象中有: list当前页分页数据和total总条数
        return new PageInfo<>(list);
    }

    /**
     * 登录
     *
     * @param loginDto
     * @return
     */
    @Override
    public String accountLogin(LoginDto loginDto) {
        // 获取前端传的账号, 密码, 类型
        String account = loginDto.getAccount();
        String password = loginDto.getPassword();
        Integer type = loginDto.getType();

        // 判断账号是否为空
        if (StrUtil.isBlank(account)) {
            throw new BusinessException("账号不能为空");
        }
        // 判断密码是否为空
        if (StrUtil.isBlank(password)) {
            throw new BusinessException("密码不能为空");
        }

        // 账号和密码都不为空, 根据账号和type去数据库查询该账号的登录信息
        Logininfo dbLogininfo = logininfoMapper.findByAccount(loginDto);

        // dbLogininfo不存在
        if (dbLogininfo == null) {
            throw new BusinessException("账号不存在或密码错误"); // 账号不存在 (返回密码错误防止透露账号信息不存在)
        }

        // dbLogininfo存在, 就判断密码是否正确
        String md5Password = SecureUtil.md5(password + dbLogininfo.getSalt()); // 将前端传来的密码进行加盐加密
        // 将md5Password与从数据库查询到的账号的加盐加密后的密码进行比较
        if (!md5Password.equals(dbLogininfo.getPassword())) { // 比较不相等
            throw new BusinessException("用户不存在或密码错误"); // 密码错误 (返回账号不存在防止透露账号信息存在)
        }

        // 判断账号的启用状态
        if (!dbLogininfo.getDisable()) { // 账号禁用
            throw new BusinessException("账号已禁用, 请联系管理员!");
        }

        // 登陆成功, 管理员才有当前登录人的所有权限和所有菜单
        Payload payload = new Payload();
        // 防止前端存储localStorage信息盐值和密码暴露, 将其设置为null
        dbLogininfo.setSalt(null);
        dbLogininfo.setPassword(null);
        payload.setLogininfo(dbLogininfo); // 将登录信息设置到payload
        if (dbLogininfo.getType() == 0) { // 管理员
            // 查询当前登录员工的所有权限
            List<String> permissions = logininfoMapper.findPermissionsById(dbLogininfo.getId());
            // 查询当前登录员工的所有菜单 (菜单树)
            List<Menu> menus = menuTreeByLogininfoId(dbLogininfo.getId());

            // 将权限和菜单设置到payload
            payload.setPermissions(permissions);
            payload.setMenus(menus);
        }

        // 调用JwtUtil生成jwtToken并返回
        String jwtToken = JwtUtil.generateToken(payload);

        return jwtToken;
    }

    private List<Menu> menuTreeByLogininfoId(Long LogininfoId) {
        // 查询当前登录员工的所有菜单
        List<Menu> menus = logininfoMapper.findMenusById(LogininfoId);
        // 创建一个List集合用来返回菜单树
        List<Menu> menuTree = new ArrayList<>();
        // 创建一个map集合用来保存当前登录员工的所有菜单, key为菜单id, value为menu对象 (减轻查询数据库压力)
        Map<Long, Menu> map = new HashMap<>();
        // 遍历menus集合
        for (Menu menu : menus) {
            // 将数据保存到map集合
            map.put(menu.getId(), menu);
        }

        // 遍历menus集合, 获取菜单树
        for (Menu menu : menus) {
            // 是顶级菜单
            if (menu.getParentId() == null) {
                // 直接放入menuTree
                menuTree.add(menu);
            } else { // 有父菜单
                // 获取父菜单id
                Long parentId = menu.getParentId();
                // 根据parentId查询父菜单对象
                Menu parentMenu = map.get(parentId);
                // 防止空指针异常
                if (parentMenu != null) {
                    // 获取父菜单的子菜单集合, 并将自己放进去
                    parentMenu.getChildren().add(menu);
                }
            }
        }
        // 返回菜单树
        return menuTree;
    }

//    public static void main(String[] args) {
//        String s = RandomUtil.randomString(32); // 获取盐值
//        String s1 = SecureUtil.md5("123456" + s); // 密码加盐加密
//        System.out.println(s);
//        System.out.println(s1);
//    }

}
